package com.crop.back.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.crop.back.common.enums.QrcodeStatus;
import com.crop.back.common.exception.QrcodeVerifyException;
import com.crop.back.common.utils.EncryptionUtil;
import com.crop.back.common.utils.TraceIdGenerator;
import com.crop.back.entity.*;
import com.crop.back.mapper.ProductMapper;
import com.crop.back.mapper.QrcodeMapper;
import com.crop.back.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.crop.back.vo.QrcodeVo;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 二维码表 服务实现类
 * </p>
 *
 * @author 作者
 * @since 2025-01-20
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class QrcodeServiceImpl extends ServiceImpl<QrcodeMapper, Qrcode> implements QrcodeService {

    private final TraceIdGenerator traceIdGenerator;

    private final ProductService productService;

    private final ProductCategoryService productCategoryService;

    private final SupplierService supplierService;

    private final ProductRecordService productRecordService;

    private final ProcessingBatchService processingBatchService;

    private final ProcessorService processorService;

    private final QualityInspectionService qualityInspectionService;

    private final Gson gson = new Gson();

    private final static String INACTIVE_QRCODE = "无效的二维码";
    private final static String EXPIRED_QRCODE = "二维码已过期";
    private final static String INVALID_QRCODE = "无效的二维码";


    @Override
    public String generateQrcode(Long batchId) {
        QrcodeData qrcodeData = QrcodeData.builder()
                .traceId(traceIdGenerator.generateTraceId(batchId))
                .batchId(batchId)
                .timestamp(System.currentTimeMillis())
                .build();
        EncryptionUtil.EncryptionParams encryptionParams = EncryptionUtil.generateParams();
        String encryptedData = EncryptionUtil.encrypt(
                gson.toJson(qrcodeData),
                encryptionParams.getKey(),
                encryptionParams.getIv()
        );
        Qrcode qrcode = new Qrcode();
        qrcode.setCode(encryptedData);
        qrcode.setEncryptionParams(encryptionParams.toJson());
        qrcode.setBatchId(batchId);
        qrcode.setValidUntil(LocalDateTime.now().plusMonths(6));
        qrcode.setStatus(QrcodeStatus.ACTIVE.getCode());
        boolean save = this.save(qrcode);
        if (!save) {
            log.error("二维码生成失败，" + "保存：" + save);
            return null;
        }
        return encryptedData;
    }

    @Override
    public QrcodeData verifyQrcode(String encryptedData) {
        LambdaQueryWrapper<Qrcode> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Qrcode::getCode, encryptedData);
        Qrcode qrcode = this.getOne(wrapper);
        log.info("二维码加密内容：" + encryptedData);
        if (qrcode == null) {
            log.error("二维码不存在" + encryptedData);
            log.error("无效的二维码" + encryptedData);
            throw new QrcodeVerifyException(INACTIVE_QRCODE);
        }
        if (LocalDateTime.now().isAfter(qrcode.getValidUntil())) {
            log.error("二维码已过期" + encryptedData);
            throw new QrcodeVerifyException(EXPIRED_QRCODE);
        }
        if (qrcode.getStatus() != QrcodeStatus.ACTIVE.getCode()) {
            log.error("二维码不可用" + encryptedData);
            throw new QrcodeVerifyException(INVALID_QRCODE);
        }
        EncryptionUtil.EncryptionParams encryptionParams = gson.fromJson(
                qrcode.getEncryptionParams(),
                EncryptionUtil.EncryptionParams.class
        );
        String decryptedData = EncryptionUtil.decrypt(
                encryptedData,
                encryptionParams.getKey(),
                encryptionParams.getIv()
        );
        qrcode.setScanCount(qrcode.getScanCount() + 1);
        this.updateById(qrcode);
        return gson.fromJson(decryptedData, QrcodeData.class);
    }

    @Override
    public QrcodeVo getQrcodeVoByBatchId(Long batchId) {
        String traceId = traceIdGenerator.generateTraceId(batchId);
        
        // 获取产品和加工信息
        CompletableFuture<ProductAndProcessing> productAndProcessingFuture = CompletableFuture
                .supplyAsync(() -> getProductInfoByBatchId(batchId));
        
        // 等待产品信息获取完成，然后异步获取其他信息
        CompletableFuture<QrcodeVo> qrcodeVoFuture = productAndProcessingFuture.thenCompose(productAndProcessing -> {
            Long productId = productAndProcessing.getProcessingInfo().getProductId();
            Long supplierId = productAndProcessing.getProductInfo().getSupplierId();

            // 异步获取产品对应的品类名称
            CompletableFuture<String> categoryNameFuture = CompletableFuture
                    .supplyAsync(() -> productCategoryService.getById(productAndProcessing.getProductInfo().getCategoryId()).getName());
            
            // 并行获取供应商、生长记录和质检信息
            CompletableFuture<Supplier> supplierFuture = CompletableFuture
                    .supplyAsync(() -> getSupplierByBatchId(supplierId));
            
            CompletableFuture<List<QrcodeVo.GrowingRecord>> growingRecordsFuture = CompletableFuture
                    .supplyAsync(() -> getGrowingRecordByBatchId(productId));
            
            CompletableFuture<List<QrcodeVo.QualityInspectionInfo>> qualityInspectionFuture = CompletableFuture
                    .supplyAsync(() -> getQualityInspectionByBatchId(batchId));
            
            // 等待所有异步操作完成并组装QrcodeVo
            return CompletableFuture.allOf(
                    supplierFuture,
                    growingRecordsFuture,
                    qualityInspectionFuture,
                    categoryNameFuture
            ).thenApply(v -> {
                QrcodeVo qrcodeVo = new QrcodeVo();
                qrcodeVo.setTraceId(traceId);
                qrcodeVo.setBatchId(batchId);
                QrcodeVo.ProductInfo productInfo = productAndProcessing.getProductInfo();
                productInfo.setCategory(categoryNameFuture.join());
                qrcodeVo.setProduct(productInfo);
                qrcodeVo.setSupplier(supplierFuture.join());
                qrcodeVo.setGrowingRecords(growingRecordsFuture.join());
                qrcodeVo.setProcessing(productAndProcessing.getProcessingInfo());
                qrcodeVo.setQualityInspection(qualityInspectionFuture.join());
                return qrcodeVo;
            });
        });
        return qrcodeVoFuture.join();
    }


    public ProductAndProcessing getProductInfoByBatchId(Long batchId) {
        LambdaQueryWrapper<ProcessingBatch> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProcessingBatch::getId, batchId);

        ProcessingBatch processingBatch = processingBatchService.getOne(wrapper);
        Product product = productService.getById(processingBatch.getProductId());

        ProductCategory category = productCategoryService.getById(product.getCategoryId());
        QrcodeVo.ProductInfo productInfo = new QrcodeVo.ProductInfo();
        BeanUtils.copyProperties(product, productInfo);

        QrcodeVo.ProcessingInfo processingInfo = new QrcodeVo.ProcessingInfo();
        BeanUtils.copyProperties(processingBatch, processingInfo);
        Processor processor = processorService.getById(processingBatch.getProcessorId());
        processingInfo.setProcessor(processor);

        return ProductAndProcessing.builder()
                .processingInfo(processingInfo)
                .processor(processor)
                .productInfo(productInfo)
                .build();
    }
    public Supplier getSupplierByBatchId(Long supplierId) {
        return supplierService.getById(supplierId);
    }
    public List<QrcodeVo.GrowingRecord> getGrowingRecordByBatchId(Long productId) {
        LambdaQueryWrapper<ProductRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ProductRecord::getProductId, productId);
        List<ProductRecord> records = productRecordService.list(wrapper);
        return records.stream().map(record -> {
            QrcodeVo.GrowingRecord growingRecord = new QrcodeVo.GrowingRecord();
            BeanUtils.copyProperties(record, growingRecord);
            return growingRecord;
        }).collect(Collectors.toList());
    }
    public List<QrcodeVo.QualityInspectionInfo> getQualityInspectionByBatchId(Long batchId) {
        LambdaQueryWrapper<QualityInspection> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(QualityInspection::getBatchId, batchId);
        List<QualityInspection> inspections = qualityInspectionService.list(wrapper);
        return inspections.stream().map(inspection -> {
            QrcodeVo.QualityInspectionInfo qualityInspectionInfo = new QrcodeVo.QualityInspectionInfo();
            BeanUtils.copyProperties(inspection, qualityInspectionInfo);
            return qualityInspectionInfo;
        }).collect(Collectors.toList());
    }

    @Override
    public Page<Qrcode> getQrcodeList(Integer status, Long batchId, LocalDateTime validUntil, Integer page, Integer size) {
        return this.baseMapper.selectPage(new Page<>(page, size), 
            new LambdaQueryWrapper<Qrcode>()
                .eq(status != null, Qrcode::getStatus, status)
                .eq(batchId != null, Qrcode::getBatchId, batchId)
                .ge(validUntil != null, Qrcode::getValidUntil, validUntil)
                .orderByDesc(Qrcode::getCreateTime)
        );
    }

    @Override
    public void updateStatus(Long id, Integer status) {
        Qrcode qrcode = new Qrcode();
        qrcode.setId(id);
        qrcode.setStatus(status);
        qrcode.setUpdateTime(LocalDateTime.now());
        this.updateById(qrcode);
    }

}
